/*
 * dirent.h - dirent API for Microsoft Visual Studio
 * 
 * Copyright (C) 2006 Toni Ronkko
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * ``Software''), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL TONI RONKKO BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 *
 * Jan 18, 2008, Toni Ronkko
 * Using FindFirstFileA and WIN32_FIND_DATAA to avoid converting string
 * between multi-byte and unicode representations.  This makes the
 * code simpler and also allows the code to be compiled under MingW.  Thanks
 * to Azriel Fasten for the suggestion.
 *
 * Mar 4, 2007, Toni Ronkko
 * Bug fix: due to the strncpy_s() function this file only compiled in
 * Visual Studio 2005.  Using the new string functions only when the
 * compiler version allows.
 * 
 * Nov  2, 2006, Toni Ronkko
 * Major update: removed support for Watcom C, MS-DOS and Turbo C to
 * simplify the file, updated the code to compile cleanly on Visual
 * Studio 2005 with both unicode and multi-byte character strings,
 * removed rewinddir() as it had a bug.
 * 
 * Aug 20, 2006, Toni Ronkko
 * Removed all remarks about MSVC 1.0, which is antiqued now.  Simplified
 * comments by removing SGML tags.
 *
 * May 14 2002, Toni Ronkko
 * Embedded the function definitions directly to the header so that no
 * source modules need to be included in the Visual Studio project.  Removed
 * all the dependencies to other projects so that this very header can be
 * used independently.
 *
 * May 28 1998, Toni Ronkko
 * First version.
 */
#ifndef DIRENT_H
#define DIRENT_H

#include <windows.h>
#include <string.h>
#include <assert.h>

typedef struct dirent
{
        /* name of current directory entry (a multi-byte character string) */
        char d_name[MAX_PATH + 1];

        /* file attributes */
WIN32_FIND_DATAA        data;
    } dirent;

typedef struct DIR
{
        /* current directory entry */
        dirent current;

        /* is there an un-processed entry in current? */
        int cached;

        /* file search handle */
        HANDLE search_handle;

        /* search pattern (3 = zero terminator + pattern "\\*") */
        char patt[MAX_PATH + 3];} DIR;

static DIR *opendir(const char *dirname);
static struct dirent *readdir(DIR *dirp);
static int closedir(DIR *dirp);

/* use the new safe string functions introduced in Visual Studio 2005 */
#if defined(_MSC_VER) && _MSC_VER >= 1400
# define STRNCPY(dest,src,size) strncpy_s((dest),(size),(src),_TRUNCATE)
#else
# define STRNCPY(dest,src,size) strncpy((dest),(src),(size))
#endif

/*
 * Open directory stream DIRNAME for read and return a pointer to the
 * internal working area that is used to retrieve individual directory
 * entries.
 */
static DIR*
opendir(const char *dirname)
{
    DIR *dirp;
    assert(dirname != NULL);
    assert(strlen (dirname) < MAX_PATH);
    
    /* construct new DIR structure */
    dirp = (DIR*)malloc(sizeof(struct DIR));
    if(dirp != NULL)
    {
        char *p;
        
        /* take directory name... */
        STRNCPY(dirp->patt, dirname, sizeof(dirp->patt));
        dirp->patt[MAX_PATH] = '\0';
        
        /* ... and append search pattern to it */
        p = strchr(dirp->patt, '\0');
        if(dirp->patt < p && *(p - 1) != '\\' && *(p - 1) != ':')
        {
            *p++ = '\\';
        }
        *p++ = '*';
        *p = '\0';
        
        /* open stream and retrieve first file */
        dirp->search_handle = FindFirstFileA(dirp->patt, &dirp->current.data);
        if(dirp->search_handle == INVALID_HANDLE_VALUE)
        {
            /* invalid search pattern? */
            free(dirp);
            return NULL;
        }
        
        /* there is an un-processed directory entry in memory now */
        dirp->cached = 1;
        
    }
    return dirp;
}

/*
 * Read a directory entry, and return a pointer to a dirent structure
 * containing the name of the entry in d_name field.  Individual directory
 * entries returned by this very function include regular files,
 * sub-directories, pseudo-directories "." and "..", but also volume labels,
 * hidden files and system files may be returned.  
 */
static struct dirent *
readdir(DIR *dirp)
{
    assert(dirp != NULL);
    
    if(dirp->search_handle == INVALID_HANDLE_VALUE)
    {
        /* directory stream was opened/rewound incorrectly or it ended normally */
        return NULL;
    }

    /* get next directory entry */
    if(dirp->cached != 0)
    {
        /* a valid directory entry already in memory */
        dirp->cached = 0;
    }
    else
    {
        /* read next directory entry from disk */
        if(FindNextFileA(dirp->search_handle, &dirp->current.data) == FALSE)
        {
            /* the very last file has been processed or an error occured */
            FindClose(dirp->search_handle);
            dirp->search_handle = INVALID_HANDLE_VALUE;
            return NULL;
        }
    }

    /* copy as a multibyte character string */
    STRNCPY(dirp->current.d_name, dirp->current.data.cFileName,
            sizeof(dirp->current.d_name));
    dirp->current.d_name[MAX_PATH] = '\0';
    
    return &dirp->current;
}

/*
 * Close directory stream opened by opendir() function.  Close of the
 * directory stream invalidates the DIR structure as well as any previously
 * read directory entry.
 */
static int closedir(DIR *dirp)
{
    assert(dirp != NULL);
    
    /* release search handle */
    if(dirp->search_handle != INVALID_HANDLE_VALUE)
    {
        FindClose(dirp->search_handle);
        dirp->search_handle = INVALID_HANDLE_VALUE;
    }

    /* release directory handle */
    free(dirp);
    return 0;
}

#endif /*DIRENT_H*/
